#include <QTextStream>
#include <QFile>
#include <QPainter>
#include <QMessageBox>  //TODO: throw exception instead of showing error prompt
#include <QTranslator>
#include <limits>
#include <cmath>

#include "fileParser.h"
#include "colorScale.h"

fileParser::fileParser()
{
    currentMode=ENone;
}

bool fileParser::parseOctaveDesc(QTextStream& t, QString& name, QString& type, int& rows, int& columns){
    QRegExp nameRx("# name: +(\\S+)");
    QRegExp typeRx("# type: +(\\S+)");
    QRegExp rowsRx("# rows: +(\\d+)");
    QRegExp columnsRx("# columns: +(\\d+)");
    QString line;

    line=t.readLine();
    if (nameRx.indexIn(line)>-1){
        name=nameRx.cap(1);
    } else {
        return false;
    }

    line=t.readLine();
    if (typeRx.indexIn(line)>-1){
        type=typeRx.cap(1);
    } else {
        return false;
    }
    if (type.compare("matrix")!=0){
        return false;
    }

    line=t.readLine();
    if (rowsRx.indexIn(line)>-1){
        rows=rowsRx.cap(1).toInt();
    } else {
        return false;
    }

    line=t.readLine();
    if (columnsRx.indexIn(line)>-1){
        columns=columnsRx.cap(1).toInt();
    } else {
        return false;
    }

    return true;    //everything OK
}

void fileParser::parse(QString fileName)
{
    currentMode=ENone;
    vertices=QVector<QPointF>();
    triangles=QVector<triangle*>();
    triangleHeight=QVector<float>();
    triangleVector=QMap<int,QPointF>();
    vertexHeight=QVector<float>();

    QFile file(fileName);
    QString line;
    QString createdByOctaveTx("# Created by Octave");
    bool createdByOctave=false;
    QRegExp positiveIntFirstRx("^\\s*(\\d+)");
    int vertexCount=0;
    int triangleCount=0;
    if ( !file.open(QIODevice::ReadOnly) )    // failed to open a file
    {
        //QMessageBox::warning(0, "Error", "Can't open file");
        throw FileNotOpenEx();
    }
    QTextStream t( &file );        // use a text stream
    int pos=0;
    do {
        line = t.readLine();
        if (line.contains(createdByOctaveTx)){
            createdByOctave=true;
        }
        pos = positiveIntFirstRx.indexIn(line);
    } while (pos<0 && !t.atEnd() && !createdByOctave);

    if (createdByOctave) {
        QString name="";
        QString type="";
        int rows=0;
        int columns=0;
        if (parseOctaveDesc(t, name, type,  rows, columns)){
            vertexCount=rows;
        } else {
            QMessageBox::warning(0, "Error", "Error when parsing matrix description in file produced by Octave");
            return;
        }
    } else {
        if (pos>=0){
            vertexCount=positiveIntFirstRx.cap(1).toInt();
        } else {
            throw BadFileStructureEx();
            //QMessageBox::warning(0, "Error", "Incorrect file structure");
            //return;
        }
    }

    int loadedVertexCount=0;
    // regular expression for float number followed by whitespace and another float number
    //TODO: check if following regullar expr. is correct, or there should be more \\d+ for first \\d
    QRegExp numberRx("^\\s*(-?\\d(\\.\\d+)?([eE]-?\\d+)?)\\s+(-?\\d(\\.\\d+)?([eE]-?\\d+)?)");
    do  {
        line = t.readLine();         // line of text excluding '\n'
        pos = numberRx.indexIn(line);
        if (pos > -1){  //maching pattern found
            float x = numberRx.cap(1).toFloat();
            float y = numberRx.cap(4).toFloat();
            vertices.append( QPointF(x,y) );
            loadedVertexCount++;
        }
    } while (loadedVertexCount<vertexCount && !t.atEnd());
    //int maxIndex=vertices.size(); //maxIndex==vertexCount
    if (loadedVertexCount!=vertexCount){
        QMessageBox::warning(0, "Error", "Incorrect file structure");
        return;
    }

    if (createdByOctave){
        QString name="";
        QString type="";
        int rows=0;
        int columns=0;
        if (parseOctaveDesc(t, name, type,  rows, columns)){
            triangleCount=rows;
        } else {
            QMessageBox::warning(0, "Error", "Error when parsing matrix description in file produced by Octave");
            return;
        }
    } else {
        pos=0;
        do {
            line = t.readLine();
            pos = positiveIntFirstRx.indexIn(line);
        } while (pos<0 && !t.atEnd());
        if (pos>=0){
            triangleCount=positiveIntFirstRx.cap(1).toInt();
        } else {
            QMessageBox::warning(0, "Error", "Incorrect file structure");
            return;
        }
    }

    int loadedTriangleCount=0;
    // regular expression for three integer numbers with whitespace between them
    QRegExp intsRx("^\\s*(\\d+)\\s+(\\d+)\\s+(\\d+)");
    do {
        line = t.readLine();         // line of text excluding '\n'
        int pos=intsRx.indexIn(line);
        if (pos>=0){    //maching pattern found
            int vert1=intsRx.cap(1).toInt();
            int vert2=intsRx.cap(2).toInt();
            int vert3=intsRx.cap(3).toInt();
            if (vert1<=vertexCount && vert2<=vertexCount && vert3<=vertexCount){
                triangle* tr = new triangle(vert1, vert2, vert3);
                triangles.append(tr);
                loadedTriangleCount++;
            } else {
                QMessageBox::warning(0, "Error", "Node index is greater than maximum node count");
                return;
            }
        }
    } while (loadedTriangleCount<triangleCount && !t.atEnd() );
    if (loadedTriangleCount!=triangleCount){
        QMessageBox::warning(0, "Error", "Wrong triangle count");
        return;
    }
    currentMode=EGridOnly;

    if (createdByOctave){
        QString name="";
        QString type="";
        int rows=0;
        int columns=0;
        if (parseOctaveDesc(t, name, type,  rows, columns)){
            if(name.compare("S0")==0){
                currentMode=EScalar0;
            }
            if (name.compare("S1")==0){
                currentMode=EScalar1;
            }
            if (name.compare("V0")==0){
                currentMode=EVector0;
            }
            if (name.compare("V1")==0){
                currentMode=EVector1;
            }
            if (currentMode==EGridOnly && !t.atEnd()){
                QMessageBox::warning(0, "Error", "Expected names of third matrix are: S0, S1, V0, V1");
                return;
            }
        } else {
            if (!t.atEnd()){
                QMessageBox::warning(0, "Error", "Error when parsing matrix description in file produced by Octave");
                return;
            }
        }
    } else {
        QRegExp funcTypeRx("^\\s*([SV])\\s*([01])");
        pos=0;
        do {
            line = t.readLine();
            pos = funcTypeRx.indexIn(line);
        } while (pos<0 && !t.atEnd());
        if (pos>=0){    //TODO: using pos for two different things, REFACTOR!
            QString type=funcTypeRx.cap(1);
            QString numType=funcTypeRx.cap(2);
            if (type.compare("S")==0){
                if (numType.compare("0")==0){
                    currentMode=EScalar0;
                }
                if (numType.compare("1")==0){
                    currentMode=EScalar1;
                }
            }
            if (type.compare("V")==0){
                if (numType.compare("0")==0){
                    currentMode=EVector0;
                }
                if (numType.compare("1")==0){
                    currentMode=EVector1;
                }
            }
        }
    }

    if (currentMode==EScalar0){
        int loadedHeightCount=0;
        QRegExp floatNumFirstRx("^\\s*(-?\\d+(\\.\\d+)?([eE]-?\\d+)?)");
        min=std::numeric_limits<float>::max();
        max=std::numeric_limits<float>::min();
        do {
            line = t.readLine();
            pos = floatNumFirstRx.indexIn(line);
            if (pos>=0){
                float height=floatNumFirstRx.cap(1).toFloat();
                if (height>max){
                    max=height;
                }
                if (height<min){
                    min=height;
                }
                triangleHeight.append(height);
                loadedHeightCount++;
            }
        } while (loadedHeightCount<triangleCount && !t.atEnd());
        if (loadedHeightCount!=triangleCount){
            QMessageBox::warning(0, "Error", "Incorrect file structure");
            currentMode=EGridOnly;
            return;
        }
    }

    //TODO: this is almost the same as EScalar0 except loadedCount is up to vertexCount instead of triangleCount
    // and there is vertexHeight container instead of triangleHeight- MERGE!
    if (currentMode==EScalar1){
        int loadedCount=0;
        QRegExp threeFloatsRx("^\\s*(-?\\d+(\\.\\d+)?([eE]-?\\d+)?)");
        min=std::numeric_limits<float>::max();
        max=std::numeric_limits<float>::min();
        do {
            line = t.readLine();
            pos = threeFloatsRx.indexIn(line);
            if (pos>=0){
                float height=threeFloatsRx.cap(1).toFloat();
                if (height>max){
                    max=height;
                }
                if (height<min){
                    min=height;
                }
                vertexHeight.append(height);
                loadedCount++;
            }
        } while (loadedCount<vertexCount && !t.atEnd());
        if (loadedCount!=vertexCount){
            QMessageBox::warning(0, "Error", "Incorrect file structure");
            currentMode=EGridOnly;
            return;
        }
    }

    bool result=true;
    if (currentMode==EVector0){
        result=loadVector(t, triangleCount, triangleVector);
    }
    if (currentMode==EVector1){
        result=loadVector(t, vertexCount, nodeVector);
    }
    if (result==false){
        QMessageBox::warning(0, "Error", "Error while loading vector values");
        currentMode=EGridOnly;
    }
    file.close();

}

bool fileParser::loadVector(QTextStream& t, int expectedCount, QMap<int,QPointF>& vectorMap){
    int loadedCount=0;
    QRegExp floatNumFirstRx("^\\s*(-?\\d+(\\.\\d+)?([eE]-?\\d+)?)\\s+(-?\\d+(\\.\\d+)?([eE]-?\\d+)?)");
    min=std::numeric_limits<float>::max();
    max=std::numeric_limits<float>::min();
    do {
        QString line = t.readLine();
        int pos = floatNumFirstRx.indexIn(line);
        if (pos>=0){
            float vector_x=floatNumFirstRx.cap(1).toFloat();
            float vector_y=floatNumFirstRx.cap(4).toFloat();
            float vector_size=sqrt(vector_x*vector_x + vector_y*vector_y);
            if (vector_size>max){
                max=vector_size;
            }
            if (vector_size<min){
                min=vector_size;
            }
            loadedCount++;
            vectorMap[loadedCount]=(QPointF(vector_x, vector_y));
        }
    } while (loadedCount<expectedCount && !t.atEnd());
    if (loadedCount!=expectedCount){
        return false;
    }
    return true;
}

void fileParser::populate(RenderPseudoThread* aThread){
    if (currentMode==EScalar1){
        aThread->populate(triangles, vertexHeight, vertices, min, max);
    }
}

void fileParser::draw(QImage *image, const QMatrix& matrix, RenderPseudoThread* thread,
                      bool grid, bool showVertexNames, bool showTriangleNames, bool showContours)
{
    if (currentMode==ENone){
        return;
    }
    QPainter* painter = new QPainter(image);
    painter->setWorldMatrix(matrix);
    QMatrix currentMatrix = painter->matrix();    //save general matrix transformation

    if (currentMode==EScalar1 && !showContours){
        thread->render(image->size(), matrix);
    }

    QVector<float>::iterator heightIt = triangleHeight.begin();
    for (QVector<triangle*>::iterator it=triangles.begin(); it!=triangles.end(); it++){
        QPointF v1=vertices[(*it)->get1()-1];
        QPointF v2=vertices[(*it)->get2()-1];
        QPointF v3=vertices[(*it)->get3()-1];

        if (showContours && currentMode==EScalar1){
            float h1( vertexHeight[(*it)->get1()-1]);
            float h2( vertexHeight[(*it)->get2()-1]);
            float h3( vertexHeight[(*it)->get3()-1]);
            Vector3D vect1(v1,h1), vect2(v2,h2), vect3(v3,h3);

            QMultiMap<float, Vector3D> sortedHeights;
            sortedHeights.insert(h1, vect1);
            sortedHeights.insert(h2, vect2);
            sortedHeights.insert(h3, vect3);
            QMultiMap<float, Vector3D>::iterator sortedIt=sortedHeights.begin();
            float z1=sortedIt.value().z();
            QPointF x1=sortedIt.value();
            sortedIt++;
            float z2=sortedIt.value().z();
            QPointF x2=sortedIt.value();
            sortedIt++;
            float z3=sortedIt.value().z();
            QPointF x3=sortedIt.value();

            int layerCount=9;  //real layer count is layerCount + 1
            float zero=1e-18;   //accurancy

            for (int i=0; i<=layerCount; i++){
                float contourHeight=min+(max-min)/layerCount*i;
                colorScale cs(min,max);
                painter->setPen(cs.getColor(contourHeight));
                if (contourHeight>=z1 && contourHeight<=z3){    //is contour in this triangle
                    if (fabs(z1-z3)<zero){ //if all vertexes has same height whole triangle is contour
                        QPolygonF pol;
                        painter->setBrush(cs.getColor(contourHeight));
                        pol << x1 << x2 << x3;
                        painter->drawPolygon(pol);
                        painter->setBrush(QBrush());
                    } else {
                        QPointF c1=x1+(contourHeight-z1)/(z3-z1)*(x3-x1);
                        QPointF c2;
                        if (contourHeight<z2){
                            if (fabs(z2-z1)>zero){
                                c2=x1+(contourHeight-z1)/(z2-z1)*(x2-x1);
                            } else {
                                c2=x2;
                            }
                        } else {
                            if (fabs(z3-z2)>zero){
                               c2=x2+(contourHeight-z2)/(z3-z2)*(x3-x2);
                           } else {
                               c2=x2;  //does this case ever hapend?
                           }
                        }
                        painter->drawLine(c1,c2);
                    }
                }
            }
            painter->setPen(Qt::black);
        }
        QPolygonF polygon;
        polygon << v1 << v2 << v3;

        if (!grid){
            painter->setPen(Qt::NoPen);
        }

        if (currentMode==EScalar0){
            colorScale cs(min,max);
            float height=*heightIt;
            QBrush brush(cs.getColor(height));
            painter->setBrush(brush);
            heightIt++;
        }
        painter->drawPolygon(polygon);
    }
    
    if (currentMode==EVector0){
        QMatrix original=painter->matrix();   //keep current transformation
        //QVector<QPointF>::iterator vectorIt=triangleVector.begin();
        int vectorIndex=1;
        for (QVector<triangle*>::iterator it=triangles.begin(); it!=triangles.end(); it++){
            QPointF v1=vertices[(*it)->get1()-1];
            QPointF v2=vertices[(*it)->get2()-1];
            QPointF v3=vertices[(*it)->get3()-1];

            colorScale cs(min,max);

            float vector_x=triangleVector[vectorIndex].x();//vectorIt.x();
            float vector_y=triangleVector[vectorIndex].y();//(*vectorIt).y();
            float angle=atan2(vector_y,vector_x);
            float vector_size=sqrt(vector_x*vector_x + vector_y*vector_y);

            //compute arrow size as average size of triangle side lengths
            QPointF side1 = v1-v2;
            QPointF side2 = v2-v3;
            QPointF side3 = v3-v1;
            float lenght1=sqrt(side1.x()*side1.x() + side1.y()*side1.y());
            float lenght2=sqrt(side2.x()*side2.x() + side2.y()*side2.y());
            float lenght3=sqrt(side3.x()*side3.x() + side3.y()*side3.y());
            float arrowLenght=(lenght1+lenght2+lenght3)/3;

            QMatrix changed=original;
            changed.translate((v1.x()+v2.x()+v3.x())/3, (v1.y()+v2.y()+v3.y())/3);
            changed.rotate(angle*180/M_PI);

            painter->setWorldMatrix(changed);
            painter->setPen(cs.getColor(vector_size));
            //draw arrow
            QVector<QLineF> lines;
            lines << QLineF(0,0,arrowLenght,0) << QLineF(arrowLenght,0, arrowLenght-arrowLenght*0.3, arrowLenght*0.1)
                           << QLineF(arrowLenght,0, arrowLenght-arrowLenght*0.3, -arrowLenght*0.1);
            painter->drawLines(lines);
            vectorIndex++;

            //vectorIt++;     //TODO: this is not nice solution
        }
    }

    if (currentMode==EVector1){
        QMatrix original=painter->matrix();   //keep current transformation
        for (int index=0; index<vertices.size(); index++){

            colorScale cs(min,max);

            float vector_x=nodeVector[index].x();//vectorIt.x();
            float vector_y=nodeVector[index].y();//(*vectorIt).y();
            float angle=atan2(vector_y,vector_x);
            float vector_size=sqrt(vector_x*vector_x + vector_y*vector_y);

            QMatrix changed=original;
            QPointF origin=vertices[index];
            changed.translate(origin.x(), origin.y());
            changed.rotate(angle*180/M_PI);

            painter->setWorldMatrix(changed);
            painter->setPen(cs.getColor(vector_size));
            //draw arrow
            int temp=1;     //TODO: remove temp when no more needed
            QVector<QLineF> lines;
            lines << QLineF(0,0,temp*0.1,0) << QLineF(temp*0.1,0, temp*0.1-temp*0.03, temp*0.01)
                           << QLineF(temp*0.1,0, temp*0.1-temp*0.03, -temp*0.01);
            painter->drawLines(lines);
        }
    }

    if (showVertexNames){
        painter->setWorldMatrixEnabled(false);
        int vertexIndex=0;  //TODO: vertex index must be member value of some new point class
        for(QVector<QPointF>::iterator vertexIt=vertices.begin(); vertexIt!=vertices.end(); vertexIt++){
            vertexIndex++;
            painter->setPen(Qt::black);
            painter->drawText(currentMatrix.map(*vertexIt), QString::number(vertexIndex));
        }
        painter->setWorldMatrixEnabled(true);
    }

    if (showTriangleNames){
        painter->setWorldMatrixEnabled(false);
        int triangleIndex=0;  //TODO: triangle index must be member value of triangle class
        for(QVector<triangle*>::iterator triangleIt=triangles.begin(); triangleIt!=triangles.end(); triangleIt++){
            triangleIndex++;
            QPointF v1=vertices[(*triangleIt)->get1()-1];
            QPointF v2=vertices[(*triangleIt)->get2()-1];
            QPointF v3=vertices[(*triangleIt)->get3()-1];
            QPointF centerOfMass = QPointF((v1.x()+v2.x()+v3.x())/3, (v1.y()+v2.y()+v3.y())/3);
            painter->setPen(Qt::yellow);
            painter->drawText(currentMatrix.map(centerOfMass), QString::number(triangleIndex));
        }
        painter->setWorldMatrixEnabled(true);
    }
    painter->end();
    delete painter;
}
